import { BufferAttribute, BufferGeometry, CanvasTexture, Color, DoubleSide, Group as TGroup, Mesh, MeshBasicMaterial, ShaderMaterial, Shape, ShapeGeometry, Sprite, Vector2, Vector3 } from "three";
import { CqkjMap } from "../Basic/CqkjMap";
import { FixAxisSprite, FixAxisSpriteMaterial } from "../Basic/FixAxisSprite";
import { MapElement, MapElementOption } from "../Basic/MapElement";
import { setValue } from "../Util/common";
import { projection } from "../Util/projection";
import { Group as ZGroup, Polygon } from 'zrender'

function createMaterial(color: number) {
  return new ShaderMaterial({
    transparent: true,
    side: DoubleSide,
    uniforms: {
      u_color: {
        value: new Color(color)
      }
    },
    vertexShader: `
    varying float v_a;
    void main(){
      v_a=uv.y;
      gl_Position= projectionMatrix * modelViewMatrix * vec4( position, 1.0 );
    }
    `,
    fragmentShader: `
    varying float v_a;
    uniform vec3 u_color;
    void main(){
      float a=max(0.1,(abs(v_a-0.5)-0.4)*10.0);
      gl_FragColor=vec4(u_color,a);
    }
    `
  })
}

const materials = [createMaterial(0x94CCB1), createMaterial(0x6CF8AC), createMaterial(0xFFD43A), createMaterial(0xFF3A3A)]
const hmaterials = [new MeshBasicMaterial({
  transparent: true,
  color: 0x94CCB1,
  opacity: 0.1
}), new MeshBasicMaterial({
  transparent: true,
  color: 0x6CF8AC,
  opacity: 0.1
}), new MeshBasicMaterial({
  transparent: true,
  color: 0xFFD43A,
  opacity: 0.1
}), new MeshBasicMaterial({
  transparent: true,
  color: 0xFF3A3A,
  opacity: 0.1
})]
const colors = ['#94CCB1',
  '#6CF8AC',
  '#FFD43A',
  '#FF3A3A',]

export interface AirspaceOption extends MapElementOption {
  points: number[][],
  height: number[],
  risk: number
}

export class Airspace extends MapElement<AirspaceOption>{

  readonly root3 = new TGroup()

  readonly root2 = new ZGroup()

  get show(): boolean {
    return this.option.show
  }

  set show(val: boolean) {
    setValue(this.option, 'show', val, () => {
      this.root3.visible = val
      val ? this.root2.show() : this.root2.hide()
    })
  }

  get wall() {
    return this.root3.children[0] as Mesh
  }

  get label() {
    return this.root3.children[1] as Sprite
  }

  get polygon() {
    return this.root3.children[2] as Mesh
  }

  get outline(){
    return this.root2.childAt(0) as Polygon
  }

  get risk(){
    return this.option.risk
  }

  set risk(val){
    setValue(this.option,'risk',val,()=>{
      this.polygon.material=hmaterials[val]
      this.wall.material=materials[val]
      this.outline.attr({
        style:{
          stroke:colors[val]
        }
      })
    })
  }

  constructor(map: CqkjMap, option: AirspaceOption) {
    super(map, option)
    const min = new Vector3(Infinity, Infinity, projection.altToZ(option.height[0]))
    const max = new Vector3(-Infinity, -Infinity, projection.altToZ(option.height[1]))

    const points = option.points.map(e => {
      const p = [projection.lonToX(e[0]), projection.latToY(e[1])]
      if (min.x > p[0]) min.x = p[0]
      if (max.x < p[0]) max.x = p[0]
      if (min.y > p[1]) min.y = p[1]
      if (max.y < p[1]) max.y = p[1]
      return p
    })
    // const [bottom,top]=option.height.map(e=>projection.altToZ(e))

    this.root3.add(createWall(points, min, max, option.risk))

    this.root3.add(createLabel(option.name, min, max))

    this.root3.add(createPolygen(points, min.z, option.risk))

    this.root2.add(createOutline(points, option.risk))

    map.scene.add(this.root3)

    map.ground.root.add(this.root2)

    this.onModeChange()
  }

  onModeChange(): void {
    if (this.map.mode == '2D') {
      this.wall.visible = false
      this.polygon.visible = true
      this.outline.show()
    } else {
      this.wall.visible = true
      this.polygon.visible = false
      this.outline.hide()
    }
  }

  focus() {
    const { map, label } = this
    map.zoomTo(label.position, 500000)
  }

  onDestroy(): void {
    const { map, label, root3 } = this
    map.scene.remove(root3)
    map.ground.root.remove(this.root2)
    label.material.map?.dispose()
    label.material.dispose()
  }

}


function createWall(points: number[][], min: Vector3, max: Vector3, risk: number) {
  const geometry = new BufferGeometry()
  const position: number[] = []
  const uv: number[] = []
  const index: number[] = []

  points.forEach((p, i) => {

    if (i) {
      const i2 = i * 2
      index.push(i2 - 2, i2, i2 - 1, i2, i2 + 1, i2 - 1)
    }
    position.push(p[0], p[1], min.z, p[0], p[1], max.z)
    uv.push(0, 0, 0, 1)
  })
  geometry.attributes.position = new BufferAttribute(new Float32Array(position), 3)
  geometry.attributes.uv = new BufferAttribute(new Float32Array(uv), 2)
  geometry.index = new BufferAttribute(new Uint16Array(index), 1)

  const mesh = new Mesh(geometry, materials[risk])
  // mesh.layers.enable(1)
  return mesh
}

function createLabel(name: string, min: Vector3, max: Vector3) {
  const canvas = document.createElement('canvas')
  canvas.width = 128
  canvas.height = 128
  const ctx = canvas.getContext('2d')!
  ctx.font = '24px OPPOSans'
  ctx.textAlign = 'center'
  ctx.textBaseline = 'middle'
  ctx.fillStyle = '#ffffff'
  ctx.fillText(name, 64, 64)

  const texture = new CanvasTexture(canvas)

  const label = new FixAxisSprite(new FixAxisSpriteMaterial({
    uniforms: {
      u_texture: { value: texture }
    },
    center: new Vector2(0.5, 0.5),
    axis: '',
    direction: new Vector3(1, 0, 0),
    size: new Vector2(0, 0),
    scale: new Vector2(0.08, 0.08),
    fragmentShader: `
    varying vec2 v_uv;
    uniform sampler2D u_texture;
    void main() { 
      gl_FragColor = texture2D(u_texture,v_uv);
    }
    `
  }))
  label.position.copy(min).add(max).divideScalar(2)
  return label
}

function createPolygen(points: number[][], height: number, risk: number) {
  const shape = new Shape(points.map(e => new Vector2(e[0], e[1])))
  const geometry = new ShapeGeometry(shape, 1)
  const mesh = new Mesh(geometry, hmaterials[risk])
  mesh.position.setZ(height)
  // mesh.layers.enable(1)
  return mesh
}

function createOutline(points: number[][], risk: number) {
  return new Polygon({
    shape: {
      points,
    },
    style: {
      fill: 'none',
      strokeNoScale: true,
      lineWidth: 1,
      stroke: colors[risk]
    }
  })
}